package epoch.game.core.script.java.loader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.jar.JarFile;

import org.apache.logging.log4j.Logger;

import epoch.game.core.common.Loggers;
import epoch.game.core.script.java.IScriptEntry;

/**
 * 脚本加载器
 * @author will
 * @date 2015年1月25日 下午2:44:16
 *
 */
public class ScriptClassLoader extends URLClassLoader {

	private static Logger log = Loggers.SCRIPT_LOGGER;
	private String baseDir=null;
	private HashSet<String> scriptNames = new HashSet<String>();
	private JarFile jarFile;

	public ScriptClassLoader(URL[] urls) {
		super(urls);
	}
	
	public ScriptClassLoader(URL[] urls, String baseDir, HashSet<String> scripts){
		super(urls);
		this.baseDir = baseDir;
		this.scriptNames = scripts;
	}
	
	public ScriptClassLoader(URL[] urls, JarFile jarFile, HashSet<String> scripts){
		super(urls);
		this.jarFile = jarFile;
		this.scriptNames = scripts;
	}

	public Class<?> customDefineClass(String className, File file) throws IOException {
		try {
			Class<?> clasz = this.findLoadedClass(className);
			if (clasz != null) {
				resolveClass(clasz);
				return clasz;
			}
		} catch (Exception ex) {
			log.error("customDefineClass>>findLoadedClass:load class err!", ex);
		}
		InputStream input = null;
		try {
			input = new FileInputStream(file);
			byte[] classBytes = new byte[(int) file.length()];
			input.read(classBytes);
			input.close();
			if(log.isDebugEnabled()){
				log.debug("begin defineClass className:"+className);
			}
			return defineClass(className, classBytes, 0, classBytes.length);
		} catch (Throwable e) {
			if(input != null){
				input.close();
			}
			log.error("className:"+className+",err:"+e, e);
			throw e;
		}
	}
	
	public Class<?> customDefineClass(String className, byte[] classBytes) throws IOException {
		try {
			Class<?> clasz = this.findLoadedClass(className);
			if (clasz != null) {
				resolveClass(clasz);
				return clasz;
			}
		} catch (Exception ex) {
			log.error("customDefineClass>>findLoadedClass:load class err!", ex);
		}
		return defineClass(className, classBytes, 0, classBytes.length);
	}
	
	/**
	 * 加载依赖的类
	 * @param className
	 * @return
	 */
	private Class<?> tryDefineClass(String className){
		Class<?> clasz = null;
		if (jarFile == null && baseDir != null) {
			String filename = baseDir + File.separator + className.replace('.', '/') + ".class";
			try {
				@SuppressWarnings("resource")
				InputStream inStream = new FileInputStream(new File(filename));
				if (inStream != null) {
					byte[] bt = new byte[inStream.available()];
					inStream.read(bt);
					inStream.close();
					return defineClass(className, bt, 0, bt.length);
				}
			} catch (IOException e) {
				log.error("tryDefineClass>>:load class err!className=" + className, e);
			}
		}
		return clasz;
	}

	@Override
	public Class<?> loadClass(String name, boolean resolve) {
		Class<?> clasz = super.findLoadedClass(name);
		if (clasz == null) {
			if(scriptNames.contains(name)){
				try {
					clasz = tryDefineClass(name);
				} catch (Exception e) {
					log.error("loadClass>>tryDefineClass:load class failed!className=" + name);
				}
			}else{
				try {
					clasz = super.findSystemClass(name);
				} catch (ClassNotFoundException e) {
					log.error("loadClass>>findSystemClass:load class failed!className="+name, e);
				}
			}
		}
		if (clasz == null) {
			try {
				clasz = super.findClass(name);
			} catch (ClassNotFoundException e) {
				log.error("loadClass>>findClass: class err!className="+name);
			}
		}
		if (resolve) {
			resolveClass(clasz);
		}
		if (clasz != null) {
			Class<?>[] interfaces = clasz.getInterfaces();
			for(Class<?> interClass : interfaces){
				if(interClass.equals(IScriptEntry.class)){
					Object obj;
					try {
						obj = clasz.newInstance();
						IScriptEntry entry = (IScriptEntry) obj;
						entry.init();					
					} catch (InstantiationException e) {
                        log.error("create entry script object err!className="+name,e);
					} catch (IllegalAccessException e) {
						log.error("entry script init err!",e);
					}
					break;
				}
			}

		}
		return clasz;
	}
}
